home *** CD-ROM | disk | FTP | other *** search
/ AmigActive 10 / AACD 10.iso / AACD / Games / MAME / src / vidhrdw / exidy440.c < prev    next >
C/C++ Source or Header  |  2000-04-04  |  17KB  |  689 lines

  1. /***************************************************************************
  2.  
  3.     Exidy 440 video system
  4.  
  5. ***************************************************************************/
  6.  
  7. #include "driver.h"
  8. #include "vidhrdw/generic.h"
  9.  
  10.  
  11. #ifndef INCLUDE_DRAW_CORE
  12.  
  13.  
  14. #define SPRITE_COUNT        40
  15. #define SPRITERAM_SIZE        (SPRITE_COUNT * 4)
  16. #define CHUNK_SIZE            8
  17. #define MAX_SCANLINE        240
  18. #define TOTAL_CHUNKS        (MAX_SCANLINE / CHUNK_SIZE)
  19.  
  20.  
  21. /* external globals */
  22. extern UINT8 exidy440_bank;
  23. extern UINT8 exidy440_topsecret;
  24.  
  25.  
  26. /* globals */
  27. UINT8 *exidy440_scanline;
  28. UINT8 *exidy440_imageram;
  29. UINT8 exidy440_firq_vblank;
  30. UINT8 exidy440_firq_beam;
  31. UINT8 topsecex_yscroll;
  32.  
  33. /* local allocated storage */
  34. static UINT8 exidy440_latched_x;
  35. static UINT8 *local_videoram;
  36. static UINT8 *local_paletteram;
  37. static UINT8 *scanline_dirty;
  38. static UINT8 *spriteram_buffer;
  39.  
  40. /* local variables */
  41. static UINT8 firq_enable;
  42. static UINT8 firq_select;
  43. static UINT8 palettebank_io;
  44. static UINT8 palettebank_vis;
  45. static UINT8 topsecex_last_yscroll;
  46.  
  47. /* function prototypes */
  48. void exidy440_vh_stop(void);
  49. void exidy440_update_firq(void);
  50. void exidy440_update_callback(int param);
  51.  
  52. static void scanline_callback(int param);
  53.  
  54. static void update_screen_8(struct osd_bitmap *bitmap, int scroll_offset);
  55. static void update_screen_16(struct osd_bitmap *bitmap, int scroll_offset);
  56.  
  57.  
  58.  
  59. /*************************************
  60.  *
  61.  *    Initialize the video system
  62.  *
  63.  *************************************/
  64.  
  65. int exidy440_vh_start(void)
  66. {
  67.     /* reset the system */
  68.     firq_enable = 0;
  69.     firq_select = 0;
  70.     palettebank_io = 0;
  71.     palettebank_vis = 0;
  72.     exidy440_firq_vblank = 0;
  73.     exidy440_firq_beam = 0;
  74.  
  75.     /* reset Top Secret variables */
  76.     topsecex_yscroll = 0;
  77.     topsecex_last_yscroll = 0;
  78.  
  79.     /* allocate a buffer for VRAM */
  80.     local_videoram = malloc(256 * 256 * 2);
  81.     if (!local_videoram)
  82.     {
  83.         exidy440_vh_stop();
  84.         return 1;
  85.     }
  86.  
  87.     /* clear it */
  88.     memset(local_videoram, 0, 256 * 256 * 2);
  89.  
  90.     /* allocate a buffer for palette RAM */
  91.     local_paletteram = malloc(512 * 2);
  92.     if (!local_paletteram)
  93.     {
  94.         exidy440_vh_stop();
  95.         return 1;
  96.     }
  97.  
  98.     /* clear it */
  99.     memset(local_paletteram, 0, 512 * 2);
  100.  
  101.     /* allocate a scanline dirty array */
  102.     scanline_dirty = malloc(256);
  103.     if (!scanline_dirty)
  104.     {
  105.         exidy440_vh_stop();
  106.         return 1;
  107.     }
  108.  
  109.     /* mark everything dirty to start */
  110.     memset(scanline_dirty, 1, 256);
  111.  
  112.     /* allocate a sprite cache */
  113.     spriteram_buffer = malloc(SPRITERAM_SIZE * TOTAL_CHUNKS);
  114.     if (!spriteram_buffer)
  115.     {
  116.         exidy440_vh_stop();
  117.         return 1;
  118.     }
  119.  
  120.     /* start the scanline timer */
  121.     timer_set(TIME_NOW, 0, scanline_callback);
  122.  
  123.     return 0;
  124. }
  125.  
  126.  
  127.  
  128. /*************************************
  129.  *
  130.  *    Tear down the video system
  131.  *
  132.  *************************************/
  133.  
  134. void exidy440_vh_stop(void)
  135. {
  136.     /* free VRAM */
  137.     if (local_videoram)
  138.         free(local_videoram);
  139.     local_videoram = NULL;
  140.  
  141.     /* free palette RAM */
  142.     if (local_paletteram)
  143.         free(local_paletteram);
  144.     local_paletteram = NULL;
  145.  
  146.     /* free the scanline dirty array */
  147.     if (scanline_dirty)
  148.         free(scanline_dirty);
  149.     scanline_dirty = NULL;
  150.  
  151.     /* free the sprite cache */
  152.     if (spriteram_buffer)
  153.         free(spriteram_buffer);
  154.     spriteram_buffer = NULL;
  155. }
  156.  
  157.  
  158.  
  159. /*************************************
  160.  *
  161.  *    Periodic scanline update
  162.  *
  163.  *************************************/
  164.  
  165. static void scanline_callback(int scanline)
  166. {
  167.     /* copy the spriteram */
  168.     memcpy(spriteram_buffer + SPRITERAM_SIZE * (scanline / CHUNK_SIZE), spriteram, SPRITERAM_SIZE);
  169.  
  170.     /* fire after the next 8 scanlines */
  171.     scanline += CHUNK_SIZE;
  172.     if (scanline >= MAX_SCANLINE)
  173.         scanline = 0;
  174.     timer_set(cpu_getscanlinetime(scanline), scanline, scanline_callback);
  175. }
  176.  
  177.  
  178.  
  179. /*************************************
  180.  *
  181.  *    Video RAM read/write
  182.  *
  183.  *************************************/
  184.  
  185. READ_HANDLER( exidy440_videoram_r )
  186. {
  187.     UINT8 *base = &local_videoram[(*exidy440_scanline * 256 + offset) * 2];
  188.  
  189.     /* combine the two pixel values into one byte */
  190.     return (base[0] << 4) | base[1];
  191. }
  192.  
  193.  
  194. WRITE_HANDLER( exidy440_videoram_w )
  195. {
  196.     UINT8 *base = &local_videoram[(*exidy440_scanline * 256 + offset) * 2];
  197.  
  198.     /* expand the two pixel values into two bytes */
  199.     base[0] = (data >> 4) & 15;
  200.     base[1] = data & 15;
  201.  
  202.     /* mark the scanline dirty */
  203.     scanline_dirty[*exidy440_scanline] = 1;
  204. }
  205.  
  206.  
  207.  
  208. /*************************************
  209.  *
  210.  *    Palette RAM read/write
  211.  *
  212.  *************************************/
  213.  
  214. READ_HANDLER( exidy440_paletteram_r )
  215. {
  216.     return local_paletteram[palettebank_io * 512 + offset];
  217. }
  218.  
  219.  
  220. WRITE_HANDLER( exidy440_paletteram_w )
  221. {
  222.     /* update palette ram in the I/O bank */
  223.     local_paletteram[palettebank_io * 512 + offset] = data;
  224.  
  225.     /* if we're modifying the active palette, change the color immediately */
  226.     if (palettebank_io == palettebank_vis)
  227.     {
  228.         int word;
  229.  
  230.         /* combine two bytes into a word */
  231.         offset = palettebank_vis * 512 + (offset & 0x1fe);
  232.         word = (local_paletteram[offset] << 8) + local_paletteram[offset + 1];
  233.  
  234.         /* extract the 5-5-5 RGB colors */
  235.         palette_change_color(offset / 2, ((word >> 10) & 31) << 3, ((word >> 5) & 31) << 3, (word & 31) << 3);
  236.     }
  237. }
  238.  
  239.  
  240.  
  241. /*************************************
  242.  *
  243.  *    Horizontal/vertical positions
  244.  *
  245.  *************************************/
  246.  
  247. READ_HANDLER( exidy440_horizontal_pos_r )
  248. {
  249.     /* clear the FIRQ on a read here */
  250.     exidy440_firq_beam = 0;
  251.     exidy440_update_firq();
  252.  
  253.     /* according to the schems, this value is only latched on an FIRQ
  254.      * caused by collision or beam */
  255.     return exidy440_latched_x;
  256. }
  257.  
  258.  
  259. READ_HANDLER( exidy440_vertical_pos_r )
  260. {
  261.     int result;
  262.  
  263.     /* according to the schems, this value is latched on any FIRQ
  264.      * caused by collision or beam, ORed together with CHRCLK,
  265.      * which probably goes off once per scanline; for now, we just
  266.      * always return the current scanline */
  267.     result = cpu_getscanline();
  268.     return (result < 255) ? result : 255;
  269. }
  270.  
  271.  
  272.  
  273. /*************************************
  274.  *
  275.  *    Interrupt and I/O control regs
  276.  *
  277.  *************************************/
  278.  
  279. WRITE_HANDLER( exidy440_control_w )
  280. {
  281.     int oldvis = palettebank_vis;
  282.  
  283.     /* extract the various bits */
  284.     exidy440_bank = data >> 4;
  285.     firq_enable = (data >> 3) & 1;
  286.     firq_select = (data >> 2) & 1;
  287.     palettebank_io = (data >> 1) & 1;
  288.     palettebank_vis = data & 1;
  289.  
  290.     /* set the memory bank for the main CPU from the upper 4 bits */
  291.     cpu_setbank(1, &memory_region(REGION_CPU1)[0x10000 + exidy440_bank * 0x4000]);
  292.  
  293.     /* update the FIRQ in case we enabled something */
  294.     exidy440_update_firq();
  295.  
  296.     /* if we're swapping palettes, change all the colors */
  297.     if (oldvis != palettebank_vis)
  298.     {
  299.         int i;
  300.  
  301.         /* pick colors from the visible bank */
  302.         offset = palettebank_vis * 512;
  303.         for (i = 0; i < 256; i++, offset += 2)
  304.         {
  305.             /* extract a word and the 5-5-5 RGB components */
  306.             int word = (local_paletteram[offset] << 8) + local_paletteram[offset + 1];
  307.             palette_change_color(i, ((word >> 10) & 31) << 3, ((word >> 5) & 31) << 3, (word & 31) << 3);
  308.         }
  309.     }
  310. }
  311.  
  312.  
  313. WRITE_HANDLER( exidy440_interrupt_clear_w )
  314. {
  315.     /* clear the VBLANK FIRQ on a write here */
  316.     exidy440_firq_vblank = 0;
  317.     exidy440_update_firq();
  318. }
  319.  
  320.  
  321.  
  322. /*************************************
  323.  *
  324.  *    Interrupt generators
  325.  *
  326.  *************************************/
  327.  
  328. void exidy440_update_firq(void)
  329. {
  330.     if (exidy440_firq_vblank || (firq_enable && exidy440_firq_beam))
  331.         cpu_set_irq_line(0, 1, ASSERT_LINE);
  332.     else
  333.         cpu_set_irq_line(0, 1, CLEAR_LINE);
  334. }
  335.  
  336.  
  337. int exidy440_vblank_interrupt(void)
  338. {
  339.     /* set the FIRQ line on a VBLANK */
  340.     exidy440_firq_vblank = 1;
  341.     exidy440_update_firq();
  342.  
  343.     /* allocate a timer to go off just before the refresh (but not for Top Secret */
  344.     if (!exidy440_topsecret)
  345.         timer_set(TIME_IN_USEC(Machine->drv->vblank_duration - 50), 0, exidy440_update_callback);
  346.  
  347.     return 0;
  348. }
  349.  
  350.  
  351.  
  352. /*************************************
  353.  *
  354.  *    IRQ callback handlers
  355.  *
  356.  *************************************/
  357.  
  358. void beam_firq_callback(int param)
  359. {
  360.     /* generate the interrupt, if we're selected */
  361.     if (firq_select && firq_enable)
  362.     {
  363.         exidy440_firq_beam = 1;
  364.         exidy440_update_firq();
  365.     }
  366.  
  367.     /* round the x value to the nearest byte */
  368.     param = (param + 1) / 2;
  369.  
  370.     /* latch the x value; this convolution comes from the read routine */
  371.     exidy440_latched_x = (param + 3) ^ 2;
  372. }
  373.  
  374.  
  375. void collide_firq_callback(int param)
  376. {
  377.     /* generate the interrupt, if we're selected */
  378.     if (!firq_select && firq_enable)
  379.     {
  380.         exidy440_firq_beam = 1;
  381.         exidy440_update_firq();
  382.     }
  383.  
  384.     /* round the x value to the nearest byte */
  385.     param = (param + 1) / 2;
  386.  
  387.     /* latch the x value; this convolution comes from the read routine */
  388.     exidy440_latched_x = (param + 3) ^ 2;
  389. }
  390.  
  391.  
  392.  
  393. /*************************************
  394.  *
  395.  *    Determine the time when the beam
  396.  *    will intersect a given pixel
  397.  *
  398.  *************************************/
  399.  
  400. double compute_pixel_time(int x, int y)
  401. {
  402.     /* assuming this is called at refresh time, compute how long until we
  403.      * hit the given x,y position */
  404.     return cpu_getscanlinetime(y) + (cpu_getscanlineperiod() * (double)x * (1.0 / 320.0));
  405. }
  406.  
  407.  
  408.  
  409. /*************************************
  410.  *
  411.  *    Update handling for shooters
  412.  *
  413.  *************************************/
  414.  
  415. void exidy440_update_callback(int param)
  416. {
  417.     /* note: we do most of the work here, because collision detection and beam detection need
  418.         to happen at 60Hz, whether or not we're frameskipping; in order to do those, we pretty
  419.         much need to do all the update handling */
  420.  
  421.     struct osd_bitmap *bitmap = Machine->scrbitmap;
  422.  
  423.     int y, i;
  424.     int xoffs, yoffs;
  425.     double time, increment;
  426.     int beamx, beamy;
  427.  
  428.     /* make sure color 256 is white for our crosshair */
  429.     palette_change_color(256, 0xff, 0xff, 0xff);
  430.  
  431.     /* redraw the screen */
  432.     if (bitmap->depth == 8)
  433.         update_screen_8(bitmap, 0);
  434.     else
  435.         update_screen_16(bitmap, 0);
  436.  
  437.     /* update the analog x,y values */
  438.     beamx = ((input_port_4_r(0) & 0xff) * 320) >> 8;
  439.     beamy = ((input_port_5_r(0) & 0xff) * 240) >> 8;
  440.  
  441.     /* The timing of this FIRQ is very important. The games look for an FIRQ
  442.         and then wait about 650 cycles, clear the old FIRQ, and wait a
  443.         very short period of time (~130 cycles) for another one to come in.
  444.         From this, it appears that they are expecting to get beams over
  445.         a 12 scanline period, and trying to pick roughly the middle one.
  446.         This is how it is implemented. */
  447.     increment = cpu_getscanlineperiod();
  448.     time = compute_pixel_time(beamx, beamy) - increment * 6;
  449.     for (i = 0; i <= 12; i++, time += increment)
  450.         timer_set(time, beamx, beam_firq_callback);
  451.  
  452.     /* draw a crosshair */
  453.     xoffs = beamx - 3;
  454.     yoffs = beamy - 3;
  455.     for (y = -3; y <= 3; y++, yoffs++, xoffs++)
  456.     {
  457.         if (yoffs >= 0 && yoffs < 240 && beamx >= 0 && beamx < 320)
  458.         {
  459.             plot_pixel(bitmap, beamx, yoffs, Machine->pens[256]);
  460.             scanline_dirty[yoffs] = 1;
  461.         }
  462.         if (xoffs >= 0 && xoffs < 320 && beamy >= 0 && beamy < 240)
  463.             plot_pixel(bitmap, xoffs, beamy, Machine->pens[256]);
  464.     }
  465. }
  466.  
  467.  
  468.  
  469. /*************************************
  470.  *
  471.  *    Standard screen refresh callback
  472.  *
  473.  *************************************/
  474.  
  475. void exidy440_vh_screenrefresh(struct osd_bitmap *bitmap, int full_refresh)
  476. {
  477.     /* if we need a full refresh, mark all scanlines dirty */
  478.     if (full_refresh)
  479.         memset(scanline_dirty, 1, 256);
  480.  
  481.     /* if we're Top Secret, do our refresh here; others are done in the update function above */
  482.     if (exidy440_topsecret)
  483.     {
  484.         /* if the scroll changed, mark everything dirty */
  485.         if (topsecex_yscroll != topsecex_last_yscroll)
  486.         {
  487.             topsecex_last_yscroll = topsecex_yscroll;
  488.             memset(scanline_dirty, 1, 256);
  489.         }
  490.  
  491.         /* redraw the screen */
  492.         if (bitmap->depth == 8)
  493.             update_screen_8(bitmap, topsecex_yscroll);
  494.         else
  495.             update_screen_16(bitmap, topsecex_yscroll);
  496.     }
  497. }
  498.  
  499.  
  500. /*************************************
  501.  *
  502.  *        Depth-specific refresh
  503.  *
  504.  *************************************/
  505.  
  506. #define ADJUST_FOR_ORIENTATION(orientation, bitmap, dst, x, y, xadv)    \
  507.     if (orientation)                                                    \
  508.     {                                                                    \
  509.         int dy = bitmap->line[1] - bitmap->line[0];                        \
  510.         int tx = x, ty = y, temp;                                        \
  511.         if (orientation & ORIENTATION_SWAP_XY)                            \
  512.         {                                                                \
  513.             temp = tx; tx = ty; ty = temp;                                \
  514.             xadv = dy / (bitmap->depth / 8);                            \
  515.         }                                                                \
  516.         if (orientation & ORIENTATION_FLIP_X)                            \
  517.         {                                                                \
  518.             tx = bitmap->width - 1 - tx;                                \
  519.             if (!(orientation & ORIENTATION_SWAP_XY)) xadv = -xadv;        \
  520.         }                                                                \
  521.         if (orientation & ORIENTATION_FLIP_Y)                            \
  522.         {                                                                \
  523.             ty = bitmap->height - 1 - ty;                                \
  524.             if ((orientation & ORIENTATION_SWAP_XY)) xadv = -xadv;        \
  525.         }                                                                \
  526.         /* can't lookup line because it may be negative! */                \
  527.         dst = (TYPE *)(bitmap->line[0] + dy * ty) + tx;                    \
  528.     }
  529.  
  530. #define INCLUDE_DRAW_CORE
  531.  
  532. #define DRAW_FUNC update_screen_8
  533. #define TYPE UINT8
  534. #include "exidy440.c"
  535. #undef TYPE
  536. #undef DRAW_FUNC
  537.  
  538. #define DRAW_FUNC update_screen_16
  539. #define TYPE UINT16
  540. #include "exidy440.c"
  541. #undef TYPE
  542. #undef DRAW_FUNC
  543.  
  544.  
  545. #else
  546.  
  547.  
  548. /*************************************
  549.  *
  550.  *        Core refresh routine
  551.  *
  552.  *************************************/
  553.  
  554. void DRAW_FUNC(struct osd_bitmap *bitmap, int scroll_offset)
  555. {
  556.     int orientation = Machine->orientation;
  557.     int xoffs, yoffs, count, scanline;
  558.     int x, y, i, sy;
  559.     UINT8 *palette;
  560.     UINT8 *sprite;
  561.  
  562.     /* recompute the palette, and mark all scanlines dirty if we need to redraw */
  563.     if (palette_recalc())
  564.         memset(scanline_dirty, 1, 256);
  565.  
  566.     /* draw any dirty scanlines from the VRAM directly */
  567.     sy = scroll_offset;
  568.     for (y = 0; y < 240; y++, sy++)
  569.     {
  570.         /* wrap at the bottom of the screen */
  571.         if (sy >= 240)
  572.             sy -= 240;
  573.  
  574.         /* only redraw if dirty */
  575.         if (scanline_dirty[sy])
  576.         {
  577.             UINT8 *src = &local_videoram[sy * 512];
  578.             TYPE *dst = (TYPE *)bitmap->line[y];
  579.             int xadv = 1;
  580.  
  581.             /* adjust if we're oriented oddly */
  582.             ADJUST_FOR_ORIENTATION(orientation, bitmap, dst, 0, y, xadv);
  583.  
  584.             /* redraw the scanline */
  585.             for (x = 0; x < 320; x++, dst += xadv)
  586.                 *dst = Machine->pens[*src++];
  587.             scanline_dirty[sy] = 0;
  588.         }
  589.     }
  590.  
  591.     /* get a pointer to the palette to look for collision flags */
  592.     palette = &local_paletteram[palettebank_vis * 512];
  593.  
  594.     /* start the count high for topsecret, which doesn't use collision flags */
  595.     count = exidy440_topsecret ? 128 : 0;
  596.  
  597.     /* draw the sprite images, checking for collisions along the way */
  598.     for (scanline = 0; scanline < MAX_SCANLINE; scanline += CHUNK_SIZE)
  599.     {
  600.         sprite = spriteram_buffer + SPRITERAM_SIZE * (scanline / CHUNK_SIZE) + (SPRITE_COUNT - 1) * 4;
  601.         for (i = 0; i < SPRITE_COUNT; i++, sprite -= 4)
  602.         {
  603.             UINT8 *src;
  604.             int image = (~sprite[3] & 0x3f);
  605.             xoffs = (~((sprite[1] << 8) | sprite[2]) & 0x1ff);
  606.             yoffs = (~sprite[0] & 0xff) + 1;
  607.  
  608.             /* skip if out of range */
  609.             if (yoffs < scanline || yoffs >= scanline + 16 + CHUNK_SIZE - 1)
  610.                 continue;
  611.  
  612.             /* get a pointer to the source image */
  613.             src = &exidy440_imageram[image * 128];
  614.  
  615.             /* account for large positive offsets meaning small negative values */
  616.             if (xoffs >= 0x1ff - 16)
  617.                 xoffs -= 0x1ff;
  618.  
  619.             /* loop over y */
  620.             sy = yoffs + scroll_offset;
  621.             for (y = 0; y < 16; y++, yoffs--, sy--)
  622.             {
  623.                 /* wrap at the top and bottom of the screen */
  624.                 if (sy >= 240)
  625.                     sy -= 240;
  626.                 else if (sy < 0)
  627.                     sy += 240;
  628.  
  629.                 /* stop if we get before the current scanline */
  630.                 if (yoffs < scanline)
  631.                     break;
  632.  
  633.                 /* only draw scanlines that are in this chunk */
  634.                 if (yoffs < scanline + CHUNK_SIZE)
  635.                 {
  636.                     UINT8 *old = &local_videoram[sy * 512 + xoffs];
  637.                     TYPE *dst = &((TYPE *)bitmap->line[yoffs])[xoffs];
  638.                     int currx = xoffs, xadv = 1;
  639.  
  640.                     /* adjust if we're oriented oddly */
  641.                     ADJUST_FOR_ORIENTATION(orientation, bitmap, dst, xoffs, yoffs, xadv);
  642.  
  643.                     /* mark this scanline dirty */
  644.                     scanline_dirty[sy] = 1;
  645.  
  646.                     /* loop over x */
  647.                     for (x = 0; x < 8; x++, dst += xadv * 2, old += 2)
  648.                     {
  649.                         int ipixel = *src++;
  650.                         int left = ipixel & 0xf0;
  651.                         int right = (ipixel << 4) & 0xf0;
  652.                         int pen;
  653.  
  654.                         /* left pixel */
  655.                         if (left && currx >= 0 && currx < 320)
  656.                         {
  657.                             /* combine with the background */
  658.                             pen = left | old[0];
  659.                             dst[0] = Machine->pens[pen];
  660.  
  661.                             /* check the collisions bit */
  662.                             if ((palette[2 * pen] & 0x80) && count++ < 128)
  663.                                 timer_set(compute_pixel_time(currx, yoffs), currx, collide_firq_callback);
  664.                         }
  665.                         currx++;
  666.  
  667.                         /* right pixel */
  668.                         if (right && currx >= 0 && currx < 320)
  669.                         {
  670.                             /* combine with the background */
  671.                             pen = right | old[1];
  672.                             dst[xadv] = Machine->pens[pen];
  673.  
  674.                             /* check the collisions bit */
  675.                             if ((palette[2 * pen] & 0x80) && count++ < 128)
  676.                                 timer_set(compute_pixel_time(currx, yoffs), currx, collide_firq_callback);
  677.                         }
  678.                         currx++;
  679.                     }
  680.                 }
  681.                 else
  682.                     src += 8;
  683.             }
  684.         }
  685.     }
  686. }
  687.  
  688. #endif
  689.